﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Tracy.DataModel.UltraExplorer;
using Tracy;
using SharedLib.XmlDataModel;
using SharedLib.DataAccess;
using Tracy.DataAccess.UltraExplorer;
using SharedLib.WinFormMVCFramework;
namespace TracyGUI.View.UIComponent
{
    public class UltraExplorer<T> where T : XmlDataModelWithAttributesAndID, new()
    {
        public const string ULTRAEXPLORER_PLACEHOLDER = "{placeholder}";

        public bool IsReady
        {
            get;
            private set;
        }

        public TreeView TreeView
        {
            get;
            private set;
        }

        public TreeNode RootNode
        {
            get;
            private set;
        }
        private List<string> _currentClassifierSet = null;
        public List<string> CurrentClassifierSet
        {
            get { return _currentClassifierSet; }
            set 
            {
                IsReady = false;
                _currentClassifierSet = value; 
            }
        }
        
        public Func<T, bool> DisplayCondition
        {
            get;
            set;
        }

        public Func<List<T>> GetAllItemScope
        {
            get;
            set;
        }

        public UltraExplorer(TreeView treeView, string allNodeName)
        {
            TreeView = treeView;
            RootNode = TreeView.Nodes.Add("All", allNodeName);
            CurrentClassifierSet = new List<string>();
        }

        public List<T> GetAllItems()
        {
            List<T> rtn = new List<T>();
            List<T> allItems = null;
            if (GetAllItemScope != null) allItems = GetAllItemScope();
            else
            {
                IDataModelProvider<T> dataProvider = TracyFacade.Instance.GetDataProvider<T>();
                allItems = dataProvider.GetItems();
            }
            foreach (T item in allItems)
            {
                if (DisplayCondition == null || DisplayCondition(item)) rtn.Add(item);
            }
            return rtn;
        }

        public void ReloadTree()
        {
            TreeView.InvokeOnUIThread(() =>
            {
                RootNode.Nodes.Clear();

                //Load all items
                List<T> allItems = GetAllItems();

                int totalCount = allItems.Count;

                SetNodeMatchedItems(RootNode, allItems);
                if (totalCount > 0)
                {
                    GenerateFilterChildNodes(RootNode);
                    RootNode.Expand();
                }
            
                IsReady = true;
            });
        }

        public void GenerateFilterChildNodes(TreeNode parentNode) 
        {
            TreeView.InvokeOnUIThread(() =>
            {
                if (parentNode.Level < CurrentClassifierSet.Count)
                {

                    IClassifierProvider<T> classifierProvider = TracyFacade.Instance.GetClassifierProvider<T>();
                    IClassifier<T> categorizer = classifierProvider.GetItem(CurrentClassifierSet[parentNode.Level]);
                    Dictionary<string, List<T>> categorieDict = categorizer.FindCategories(GetMatchedItems(parentNode));


                    foreach (string key in categorieDict.Keys)
                    {
                        CreateLeafNode(parentNode, categorizer, categorieDict, key);
                    }
                }
            });
        }

        private void CreateLeafNode(TreeNode parentNode, IClassifier<T> categorizer, Dictionary<string, List<T>> categorieDict, string key)
        {
            List<T> items = categorizer.GetItems(GetMatchedItems(parentNode), key);
            TreeNode childNode = parentNode.Nodes.Add(key, categorizer.GetCategoryName(key));
            SetNodeMatchedItems(childNode, items);

            //Add place holder node (So that user will see the expend icon)
            if (childNode.Level < CurrentClassifierSet.Count)
            {
                childNode.Nodes.Add(ULTRAEXPLORER_PLACEHOLDER, "");
            }
        }

        public List<T> GetMatchedItems(TreeNode node)
        {
            if (node == null) return new List<T>();
            List<T> rtn = new List<T>();
            IDataModelWithIDProvider<T> dataProvider = TracyFacade.Instance.GetDataProvider<T>();
            foreach(string key in (node.Tag as string[])){
                rtn.Add(dataProvider.GetItemByID(key));
            }
            return rtn;
        }

        public bool IsLastGeneratedNode(TreeNode node)
        {
            return node.Nodes.Count == 1 && node.Nodes[0].Name == ULTRAEXPLORER_PLACEHOLDER;
        }

        /// <summary>
        /// Is the item matching the classifier category of the given node?
        /// </summary>
        /// <param name="item"></param>
        /// <param name="node"></param>
        /// <returns></returns>
        private bool IsItemMatching(T item, TreeNode node)
        {
            if (item == null && node == null) return false;
            if (node == RootNode) return true;
            IClassifierProvider<T> classifierProvider = TracyFacade.Instance.GetClassifierProvider<T>();
            IClassifier<T> categorizer = classifierProvider.GetItem(CurrentClassifierSet[node.Level - 1]);
            if (categorizer == null) throw new Exception("UE node level out of range");
            return categorizer.MatchCategory(item, node.Name);
        }

        /// <summary>
        /// Is the item already in the matched item list of the given node?
        /// </summary>
        /// <param name="item"></param>
        /// <param name="node"></param>
        /// <returns></returns>
        private bool IsItemMatched(T item, TreeNode node)
        {
            if (item == null && node == null) return false;
            foreach (T matchedItem in GetMatchedItems(node))
            {
                if (matchedItem.ID == item.ID) return true;
            }
            return false;
        }

        private bool IsCategoryExisting(TreeNode node, string key)
        {
            foreach (TreeNode childNode in node.Nodes)
            {
                if (String.Equals(childNode.Name, key, StringComparison.OrdinalIgnoreCase)) return true;
            }
            return false;
        }

        public void UpdateTreeForItemChange(T item)
        {
            TreeView.InvokeOnUIThread(() =>
            {
                if (IsReady)
                {
                    UpdateTreeForItemChange(item, RootNode, GetAllItems());
                }
            });
        }

        private void UpdateTreeForItemChange(T item, TreeNode node, List<T> newItemList)
        {
            if (item != null && node != null)
            {
                if(GetNodeCount(node) != newItemList.Count) SetNodeMatchedItems(node, newItemList);

                if (!IsLastGeneratedNode(node) && node.Level < CurrentClassifierSet.Count)
                {
                    //Re-calculate categories
                    IClassifierProvider<T> classifierProvider = TracyFacade.Instance.GetClassifierProvider<T>();
                    IClassifier<T> categorizer = classifierProvider.GetItem(CurrentClassifierSet[node.Level]);
                    Dictionary<string, List<T>> categorieDict = categorizer.FindCategories(GetMatchedItems(node));
                    
                    //Remove Empty Nodes
                    TreeNode[] nodesToCheck = new TreeNode[node.Nodes.Count];
                    node.Nodes.CopyTo(nodesToCheck, 0);
                    foreach (TreeNode childNode in nodesToCheck)
                    {
                        bool categoryExisting = false;
                        foreach (string key in categorieDict.Keys)
                        {
                            if (String.Equals(childNode.Name, key, StringComparison.OrdinalIgnoreCase))
                            {
                                categoryExisting = true;
                                break;
                            }
                            
                        }
                        if (!categoryExisting) node.Nodes.Remove(childNode);
                    }

                    //Create or Update Nodes
                    foreach (string key in categorieDict.Keys)
                    {
                        bool categoryExisting = false;
                        foreach (TreeNode childNode in node.Nodes)
                        {
                            if (String.Equals(childNode.Name, key, StringComparison.OrdinalIgnoreCase))
                            {
                                List<T> newChildItems = categorieDict[key];
                                //if (GetNodeCount(childNode) != newChildItems.Count)
                                //{
                                UpdateTreeForItemChange(item, childNode, newChildItems);
                                //}
                                categoryExisting = true;
                                break;
                            }
                        }

                        //Create Node if not existing
                        if (!categoryExisting) CreateLeafNode(node, categorizer, categorieDict, key);
                    }

                }
            }
        }

        public int GetNodeCount(TreeNode node)
        {
            return GetMatchedItems(node).Count;
        }

        public void SetNodeMatchedItems(TreeNode node, List<T> items)
        {
            if (node == null || items == null) return;
            int index = node.Text.LastIndexOf(" (");
            if (index >= 0) node.Text = node.Text.Remove(index);
            
            if (items.Count > 0) node.Text = node.Text + " (" + items.Count.ToString() + ")";

            string[] keys = new string[items.Count];
            for (int i = 0; i < items.Count; i++) keys[i] = items[i].ID;
            node.Tag = keys;
        }
    }
}
